package minio.service.impl;


import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Multimap;
import io.minio.PutObjectArgs;
import io.minio.errors.*;
import io.minio.messages.Part;
import minio.entity.FileRecord;
import minio.mapper.FileRecordMapper;
import minio.service.FileService;
import minio.util.MinioUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


@Service
public class FileServiceImpl extends ServiceImpl<FileRecordMapper, FileRecord> implements FileService {


    /**
     * 不要加事务
     *
     * @param file
     * @param md5
     * @return
     * @throws IOException
     * @throws ServerException
     * @throws InsufficientDataException
     * @throws ErrorResponseException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws XmlParserException
     * @throws InvalidResponseException
     * @throws InternalException
     */
    @Override
    public String upload(MultipartFile file, String md5) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
        FileRecord record = getOne(Wrappers.<FileRecord>lambdaQuery().eq(FileRecord::getMd5, md5));
        if (null == record) {
            return notExistRecord(file, md5);
        } else {
            return existRecord(record, file);
        }

    }


    public String notExistRecord(MultipartFile file, String md5) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        PutObjectArgs args = getPutObjectArgs(file);
        Multimap<String, String> headers = MinioUtil.getHeaders(args);
        String uploadId = null;
        if (args.partCount() > 1) {
            uploadId = MinioUtil.createUploadId(args, headers);
        }
        FileRecord record = new FileRecord(md5, uploadId, args.object(), args.objectSize(), args.partCount());
        save(record);
        return doUpload(args, 1, uploadId, headers, record);
    }

    public String existRecord(FileRecord record, MultipartFile file) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {

        if (record.getIsUploaded() == 0) {
            PutObjectArgs args = getPutObjectArgs(file);
            return doUpload(args, record.getCompletedParts() + 1, record.getUploadId(), MinioUtil.getHeaders(args), record);
        }
        return record.getFileUrl();

    }


   public  PutObjectArgs getPutObjectArgs(MultipartFile file) throws IOException {
       InputStream stream = file.getInputStream();
       return PutObjectArgs.builder()
               .object(file.getOriginalFilename())
               .bucket(MinioUtil.BUCKET_NAME)
               .contentType("application/octet-stream")
               .stream(stream, stream.available(), MinioUtil.PART_MAX_SIZE).build();
    }

    public String doUpload(PutObjectArgs args, Integer currentPart, String uploadId, Multimap<String, String> headers, FileRecord file) throws IOException, InternalException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException {
        String fileUrl = MinioUtil.URL + "/" + args.bucket() + "/" + args.object();
        if (null == uploadId || "".equals(uploadId)) {
            MinioUtil.upload(args);
            return fileUrl;
        }

        long uploadedSize = 0L;
        int partCount = args.partCount();
        long partSize = args.partSize();
        long objectSize = args.objectSize();
        BufferedInputStream data = args.stream();
        Part[] parts = new Part[partCount];
        for (int partNumber = currentPart; partNumber <= partCount; ++partNumber) {
            long availableSize = partSize;
            if (partNumber == partCount) {
                availableSize = objectSize - uploadedSize;
            }
            String etag = MinioUtil.uploadPart(args, data, availableSize, uploadId, partNumber, headers);
            file.setCompletedParts(file.getCompletedParts() + 1);
            this.updateById(file);
            parts[partNumber - 1] = new Part(partNumber, etag);
            uploadedSize += availableSize;
        }
        MinioUtil.completeMultipartUpload(args, uploadId, parts);
        file.setIsUploaded(1);
        file.setFileUrl(fileUrl);
        this.updateById(file);
        return fileUrl;
    }
}
